Explora el funcionamiento interno de los motores de JavaScript: V8, SpiderMonkey y JavaScriptCore. Optimiza tu c贸digo para un rendimiento global.
Rendimiento en Tiempo de Ejecuci贸n de JavaScript: Un An谩lisis Profundo de V8, SpiderMonkey y JavaScriptCore
JavaScript se ha convertido en la lengua franca de la web, potenciando desde interfaces de usuario interactivas hasta aplicaciones del lado del servidor. Comprender los motores que ejecutan este c贸digo es crucial para cualquier desarrollador web que aspire a un rendimiento 贸ptimo. Este art铆culo ofrece una visi贸n general completa de los tres motores principales de JavaScript: V8 (utilizado por Chrome y Node.js), SpiderMonkey (utilizado por Firefox) y JavaScriptCore (utilizado por Safari).
Entendiendo los Motores de JavaScript
Los motores de JavaScript son componentes de software responsables de analizar, compilar y ejecutar c贸digo JavaScript. Son el coraz贸n de cualquier navegador o entorno de ejecuci贸n que admita JavaScript. Estos motores traducen c贸digo legible por humanos en instrucciones ejecutables por la m谩quina, optimizando el proceso en el camino para ofrecer una experiencia de usuario r谩pida y receptiva.
Las tareas principales que realiza un motor de JavaScript incluyen:
- An谩lisis (Parsing): Descomponer el c贸digo fuente en un 脕rbol de Sintaxis Abstracta (AST), una representaci贸n jer谩rquica de la estructura del c贸digo.
- Compilaci贸n: Transformar el AST en c贸digo m谩quina, que el ordenador puede ejecutar directamente. Esto puede implicar varias t茅cnicas de optimizaci贸n.
- Ejecuci贸n: Ejecutar el c贸digo m谩quina compilado, gestionar la memoria y manejar las interacciones con el Modelo de Objetos del Documento (DOM) en navegadores web u otros entornos de ejecuci贸n.
- Recolecci贸n de Basura (Garbage Collection): Recuperar autom谩ticamente la memoria que ya no est谩 siendo utilizada por el programa. Esto previene fugas de memoria y mantiene la aplicaci贸n funcionando sin problemas.
Los Actores Clave: V8, SpiderMonkey y JavaScriptCore
Echemos un vistazo m谩s de cerca a los principales contendientes en el 谩mbito de los motores de JavaScript:
V8
Desarrollado por Google, V8 es el motor que impulsa Google Chrome y Node.js. Es conocido por su alto rendimiento, gracias a sus sofisticadas t茅cnicas de optimizaci贸n. V8 compila JavaScript directamente a c贸digo m谩quina nativo antes de la ejecuci贸n, un proceso conocido como compilaci贸n Just-In-Time (JIT). Tambi茅n cuenta con un sofisticado recolector de basura dise帽ado para el rendimiento.
Caracter铆sticas Clave de V8:
- Compilaci贸n JIT: V8 utiliza un compilador JIT para convertir JavaScript en c贸digo m谩quina optimizado en tiempo de ejecuci贸n. Esto permite una ejecuci贸n m谩s r谩pida y una optimizaci贸n adaptativa basada en c贸mo se utiliza el c贸digo.
- Cach茅 en L铆nea (Inline Caching): V8 utiliza cach茅 en l铆nea para acelerar el acceso a propiedades. Recuerda los tipos de objetos y almacena en cach茅 las ubicaciones de sus propiedades, evitando b煤squedas de propiedades costosas.
- Compilaci贸n Optimista: V8 a menudo hace suposiciones sobre los tipos de valores y la estructura del c贸digo, optimizando en consecuencia. Si esas suposiciones resultan ser incorrectas, puede desoptimizar y recompilar el c贸digo.
- Recolecci贸n de Basura Eficiente: El recolector de basura de V8 est谩 dise帽ado para identificar y recuperar r谩pidamente la memoria no utilizada, minimizando las pausas y garantizando una experiencia de usuario receptiva.
Casos de Uso: Navegador Chrome, tiempo de ejecuci贸n del lado del servidor Node.js, aplicaciones creadas con frameworks como Angular, React y Vue.js.
Ejemplo de Impacto Global: El rendimiento de V8 ha impactado significativamente la usabilidad de las aplicaciones web a nivel mundial. Por ejemplo, aplicaciones utilizadas para educaci贸n en l铆nea, como Coursera (con usuarios en pa铆ses como India y Brasil), dependen en gran medida de la velocidad y eficiencia de V8 para ofrecer una experiencia de aprendizaje fluida. Adem谩s, Node.js, impulsado por V8, se ha convertido en una tecnolog铆a central para la construcci贸n de aplicaciones escalables del lado del servidor utilizadas en numerosas industrias en todo el mundo.
SpiderMonkey
Desarrollado por Mozilla, SpiderMonkey es el motor de JavaScript que impulsa Firefox. Fue el primer motor de JavaScript creado y tiene una larga historia de innovaci贸n. SpiderMonkey se centra en el cumplimiento de est谩ndares y proporciona un equilibrio entre rendimiento y caracter铆sticas. Tambi茅n utiliza compilaci贸n JIT, pero con diferentes estrategias de optimizaci贸n que V8.
Caracter铆sticas Clave de SpiderMonkey:
- Compilaci贸n JIT: Similar a V8, SpiderMonkey utiliza compilaci贸n JIT para mejorar el rendimiento.
- Compilaci贸n por Niveles (Tiered Compilation): SpiderMonkey utiliza un enfoque de compilaci贸n por niveles, comenzando con un compilador r谩pido pero menos optimizado y pasando a un compilador optimizador m谩s agresivo, pero m谩s lento, cuando es necesario.
- Cumplimiento de Est谩ndares: SpiderMonkey es conocido por su s贸lido soporte de los est谩ndares ECMAScript.
- Recolecci贸n de Basura: SpiderMonkey tiene un recolector de basura sofisticado dise帽ado para manejar tareas complejas de gesti贸n de memoria.
Casos de Uso: Navegador Firefox, Firefox OS (obsoleto).
Ejemplo de Impacto Global: El enfoque de Firefox en la privacidad y seguridad del usuario, combinado con el rendimiento de SpiderMonkey, lo ha convertido en un navegador popular en todo el mundo, especialmente en regiones donde la privacidad es primordial, como partes de Europa y Asia. SpiderMonkey garantiza que las aplicaciones web, utilizadas para fines que van desde la banca en l铆nea hasta las redes sociales, operen de manera eficiente y segura dentro del ecosistema de Firefox.
JavaScriptCore
Desarrollado por Apple, JavaScriptCore (tambi茅n conocido como Nitro) es el motor utilizado en Safari y otros productos de Apple, incluidas las aplicaciones basadas en WebKit. JavaScriptCore se centra en el rendimiento y la eficiencia, especialmente en el hardware de Apple. Tambi茅n emplea compilaci贸n JIT y otras t茅cnicas de optimizaci贸n para ofrecer una ejecuci贸n r谩pida de JavaScript.
Caracter铆sticas Clave de JavaScriptCore:
- Compilaci贸n JIT: JavaScriptCore, al igual que V8 y SpiderMonkey, utiliza compilaci贸n JIT para obtener ganancias de rendimiento.
- Tiempo de Inicio R谩pido: JavaScriptCore est谩 optimizado para un inicio r谩pido, un factor cr铆tico para dispositivos m贸viles y experiencias de navegaci贸n web.
- Gesti贸n de Memoria: JavaScriptCore incluye t茅cnicas avanzadas de gesti贸n de memoria para garantizar una utilizaci贸n eficiente de los recursos.
- Integraci贸n con WebAssembly: JavaScriptCore tiene un fuerte soporte para WebAssembly, lo que permite un rendimiento casi nativo para tareas computacionalmente intensivas.
Casos de Uso: Navegador Safari, aplicaciones basadas en WebKit (incluidas aplicaciones de iOS y macOS), aplicaciones creadas con frameworks como React Native (en iOS).
Ejemplo de Impacto Global: Las optimizaciones de JavaScriptCore contribuyen al rendimiento fluido de las aplicaciones web y las aplicaciones nativas de iOS en todos los dispositivos Apple a nivel mundial. Esto es particularmente importante para regiones como Am茅rica del Norte, Europa y partes de Asia, donde los productos de Apple son ampliamente utilizados. Adem谩s, JavaScriptCore es fundamental para garantizar el rendimiento r谩pido de aplicaciones como las utilizadas en telemedicina y colaboraci贸n remota, herramientas cruciales para una fuerza laboral y un sistema de salud globales.
Benchmarking y Comparaciones de Rendimiento
Comparar el rendimiento de los motores de JavaScript requiere benchmarking. Varias herramientas se utilizan para medir el rendimiento, incluyendo:
- SunSpider: Una suite de benchmarking de Apple que mide el rendimiento del c贸digo JavaScript en diversas 谩reas, como manipulaci贸n de cadenas, operaciones matem谩ticas y criptograf铆a. (Obsoleto, pero a煤n relevante para comparaciones hist贸ricas).
- JetStream: Una suite de benchmarking de Apple que se centra en una gama m谩s amplia de caracter铆sticas y capacidades de los motores de JavaScript, incluidos patrones de aplicaciones web m谩s modernos.
- Octane: Una suite de benchmarking de Google (obsoleto) que fue dise帽ada para probar el rendimiento de los motores de JavaScript en una variedad de casos de uso del mundo real.
- Kraken: Otro benchmark popular, dise帽ado para probar el rendimiento de los motores de JavaScript en navegadores web.
Tendencias Generales de los Benchmarks:
Es importante reconocer que las puntuaciones de los benchmarks pueden variar seg煤n la prueba espec铆fica, el hardware utilizado y la versi贸n del motor de JavaScript. Sin embargo, algunas tendencias generales surgen de estos benchmarks:
- V8 a menudo est谩 a la vanguardia en t茅rminos de rendimiento bruto, particularmente en tareas computacionalmente intensivas. Esto se debe principalmente a sus agresivas estrategias de optimizaci贸n y t茅cnicas de compilaci贸n JIT.
- SpiderMonkey generalmente proporciona un buen equilibrio entre rendimiento y cumplimiento de est谩ndares. Firefox a menudo se centra en una s贸lida experiencia para desarrolladores y el cumplimiento de los est谩ndares web.
- JavaScriptCore est谩 altamente optimizado para dispositivos Apple, ofreciendo un rendimiento impresionante en esas plataformas. A menudo est谩 optimizado para tiempos de inicio r谩pidos y un uso eficiente de la memoria, que son vitales para las aplicaciones m贸viles.
Advertencias Importantes:
- Las Puntuaciones de Benchmarks No Cuentan Toda la Historia: Los benchmarks ofrecen una instant谩nea del rendimiento en condiciones espec铆ficas. El rendimiento real puede verse afectado por muchos factores, incluida la complejidad del c贸digo, la conexi贸n de red y el hardware del usuario.
- El Rendimiento Var铆a con el Tiempo: Los motores de JavaScript se actualizan y mejoran constantemente, lo que significa que el rendimiento puede cambiar con cada nueva versi贸n.
- Enf贸cate en la Optimizaci贸n, No Solo en la Elecci贸n del Motor: Si bien la elecci贸n del motor de JavaScript impacta el rendimiento, optimizar tu c贸digo suele ser el factor m谩s importante. Incluso en motores m谩s lentos, el c贸digo bien escrito puede ejecutarse m谩s r谩pido que el c贸digo mal optimizado en un motor m谩s r谩pido.
Optimizaci贸n del C贸digo JavaScript para el Rendimiento
Independientemente del motor de JavaScript que se utilice, optimizar tu c贸digo es crucial para una aplicaci贸n web r谩pida y receptiva. Aqu铆 hay algunas 谩reas clave en las que centrarse:
1. Minimizar la Manipulaci贸n del DOM
Manipular directamente el DOM (Document Object Model) es un proceso relativamente lento. Reduce el n煤mero de operaciones del DOM mediante:
- Agrupaci贸n de actualizaciones del DOM: Realiza m煤ltiples cambios en el DOM a la vez. Utiliza fragmentos de documento para construir una estructura fuera de pantalla y luego adj煤ntala al DOM.
- Uso de clases CSS: En lugar de modificar directamente las propiedades CSS con JavaScript, utiliza clases CSS para aplicar estilos.
- Almacenamiento en cach茅 de elementos DOM: Almacena referencias a elementos DOM en variables para evitar consultar el DOM repetidamente.
Ejemplo: Imagina actualizar una lista de elementos en una aplicaci贸n web utilizada a nivel mundial. En lugar de agregar cada elemento individualmente al DOM dentro de un bucle, crea un fragmento de documento y agrega todos los elementos de la lista al fragmento primero. Luego, adjunta todo el fragmento al DOM. Esto reduce el n煤mero de reflows y repaints, mejorando el rendimiento.
2. Optimizar Bucles
Los bucles son una fuente com煤n de cuellos de botella en el rendimiento. Optim铆zalos mediante:
- Evitar c谩lculos innecesarios dentro del bucle: Precalcula valores si se utilizan varias veces dentro del bucle.
- Almacenamiento en cach茅 de longitudes de array: Almacena la longitud de un array en una variable para evitar recalcularla repetidamente.
- Elegir el tipo de bucle correcto: Por ejemplo, usar bucles `for` suele ser m谩s r谩pido que los bucles `for...in` al iterar sobre arrays.
Ejemplo: Considera un sitio de comercio electr贸nico que muestra informaci贸n de productos. Optimizar los bucles utilizados para renderizar cientos o incluso miles de tarjetas de productos puede mejorar dr谩sticamente los tiempos de carga de la p谩gina. El almacenamiento en cach茅 de longitudes de array y el pre-c谩lculo de valores relacionados con el producto dentro del bucle contribuyen significativamente a un proceso de renderizado m谩s r谩pido.
3. Reducir Llamadas a Funciones
Las llamadas a funciones tienen una cierta sobrecarga. Minim铆zalas mediante:
- Inlining de funciones cortas: Si una funci贸n es simple y se llama con frecuencia, considera incorporar su c贸digo directamente.
- Reducir el n煤mero de argumentos pasados a las funciones: Utiliza objetos para agrupar argumentos relacionados.
- Evitar la recursi贸n excesiva: La recursi贸n puede ser lenta. Considera usar soluciones iterativas siempre que sea posible.
Ejemplo: Considera un men煤 de navegaci贸n global utilizado en una aplicaci贸n web. Las llamadas excesivas a funciones para renderizar elementos de men煤 individuales pueden ser un cuello de botella en el rendimiento. Optimizar estas funciones reduciendo el n煤mero de argumentos y utilizando inlining mejora significativamente la velocidad de renderizado.
4. Usar Estructuras de Datos Eficientes
La elecci贸n de la estructura de datos puede tener un impacto significativo en el rendimiento.
- Utiliza arrays para datos ordenados: Los arrays son generalmente eficientes para acceder a elementos por 铆ndice.
- Utiliza objetos (o Maps) para pares clave-valor: Los objetos son eficientes para buscar valores por clave. Los Maps ofrecen m谩s caracter铆sticas y mejor rendimiento en ciertos casos de uso, especialmente cuando las claves no son cadenas.
- Considera usar Sets para valores 煤nicos: Los Sets proporcionan pruebas de membres铆a eficientes.
Ejemplo: En una aplicaci贸n global que rastrea datos de usuarios, usar un `Map` para almacenar perfiles de usuario (donde el ID de usuario es la clave) ofrece acceso y gesti贸n eficientes de la informaci贸n del usuario en comparaci贸n con el uso de objetos anidados o estructuras de datos innecesariamente complejas.
5. Minimizar el Uso de Memoria
El uso excesivo de memoria puede provocar problemas de rendimiento y pausas de recolecci贸n de basura. Reduce el uso de memoria mediante:
- Liberar referencias a objetos que ya no son necesarios: Establece variables en `null` cuando hayas terminado con ellas.
- Evitar fugas de memoria: Aseg煤rate de no retener involuntariamente referencias a objetos.
- Utilizar tipos de datos apropiados: Elige tipos de datos que utilicen la menor cantidad de memoria necesaria.
- Retrasar la carga: Para los elementos fuera del viewport en una p谩gina, retrasa la carga de im谩genes hasta que un usuario se desplace hacia ellos para reducir el uso inicial de memoria.
Ejemplo: En una aplicaci贸n de mapas global, como Google Maps, la gesti贸n eficiente de la memoria es crucial. Los desarrolladores deben evitar fugas de memoria relacionadas con los marcadores, formas y otros elementos. Liberar adecuadamente las referencias a estos elementos del mapa cuando ya no son visibles evita el consumo excesivo de memoria y mejora la experiencia del usuario.
6. Usar Web Workers para Tareas en Segundo Plano
Los Web Workers te permiten ejecutar c贸digo JavaScript en segundo plano, sin bloquear el hilo principal. Esto es 煤til para tareas computacionalmente intensivas u operaciones de larga duraci贸n.
- Descargar operaciones intensivas en CPU: Delega tareas como procesamiento de im谩genes, an谩lisis de datos y c谩lculos complejos a web workers.
- Evitar bloquear el hilo de la interfaz de usuario: Aseg煤rate de que la interfaz de usuario permanezca receptiva durante las operaciones de larga duraci贸n.
Ejemplo: En una aplicaci贸n cient铆fica global que requiere simulaciones complejas, descargar los c谩lculos de simulaci贸n a web workers garantiza que la interfaz de usuario permanezca interactiva, incluso durante procesos computacionalmente intensivos. Esto permite al usuario seguir interactuando con otros aspectos de la aplicaci贸n mientras se ejecuta la simulaci贸n.
7. Optimizar Solicitudes de Red
Las solicitudes de red a menudo son un cuello de botella importante en las aplicaciones web. Optim铆zalas mediante:
- Minimizar el n煤mero de solicitudes: Combina archivos CSS y JavaScript, y utiliza sprites CSS.
- Usar cach茅: Aprovecha la cach茅 del navegador y la cach茅 del lado del servidor para reducir la necesidad de volver a descargar recursos.
- Comprimir activos: Comprime im谩genes y otros activos para reducir su tama帽o.
- Utilizar una Red de Entrega de Contenidos (CDN): Distribuye tus activos en m煤ltiples servidores para reducir la latencia para usuarios de todo el mundo.
- Implementar carga diferida (lazy loading): Retrasa la carga de im谩genes y otros recursos que no son inmediatamente visibles.
Ejemplo: Una plataforma internacional de comercio electr贸nico utiliza CDNs para distribuir sus recursos en m煤ltiples regiones geogr谩ficas. Esto reduce los tiempos de carga para usuarios en diferentes pa铆ses y proporciona una experiencia de usuario m谩s r谩pida y consistente.
8. Divisi贸n de C贸digo (Code Splitting)
La divisi贸n de c贸digo es una t茅cnica que divide tu paquete de JavaScript en fragmentos m谩s peque帽os, que se pueden cargar bajo demanda. Esto puede mejorar significativamente el tiempo de carga inicial de la p谩gina.
- Cargar solo el c贸digo necesario inicialmente: Divide tu c贸digo en m贸dulos y carga solo los m贸dulos que se requieren para la p谩gina actual.
- Usar importaciones din谩micas: Utiliza importaciones din谩micas para cargar m贸dulos bajo demanda.
Ejemplo: Una aplicaci贸n que proporciona servicios en todo el mundo puede mejorar la velocidad de carga mediante la divisi贸n de c贸digo. Solo el c贸digo requerido para la ubicaci贸n actual del usuario se carga en la carga inicial de la p谩gina. M贸dulos adicionales con idiomas y caracter铆sticas espec铆ficas de la ubicaci贸n se cargan din谩micamente cuando son necesarios.
9. Usar un Generador de Perfiles de Rendimiento
Un generador de perfiles de rendimiento es una herramienta esencial para identificar cuellos de botella en el rendimiento de tu c贸digo.
- Utilizar herramientas de desarrollador del navegador: Los navegadores modernos incluyen generadores de perfiles de rendimiento integrados que te permiten analizar la ejecuci贸n de tu c贸digo e identificar 谩reas de optimizaci贸n.
- Analizar el uso de CPU y memoria: Utiliza el generador de perfiles para rastrear el uso de CPU, la asignaci贸n de memoria y la actividad de recolecci贸n de basura.
- Identificar funciones y operaciones lentas: El generador de perfiles destacar谩 las funciones y operaciones que tardan m谩s en ejecutarse.
Ejemplo: Utilizando la pesta帽a de rendimiento de Chrome DevTools para analizar una aplicaci贸n web utilizada por usuarios de todo el mundo, un desarrollador puede identificar f谩cilmente cuellos de botella en el rendimiento, como llamadas a funciones lentas o fugas de memoria, y abordarlos para mejorar la experiencia del usuario en todas las regiones.
Consideraciones para la Internacionalizaci贸n y Localizaci贸n
Al desarrollar aplicaciones web para una audiencia global, es crucial considerar la internacionalizaci贸n y la localizaci贸n. Esto implica adaptar tu aplicaci贸n a diferentes idiomas, culturas y preferencias regionales.
- Codificaci贸n de caracteres adecuada (UTF-8): Utiliza la codificaci贸n de caracteres UTF-8 para admitir una amplia gama de caracteres de diferentes idiomas.
- Localizaci贸n de texto: Traduce el texto de tu aplicaci贸n a varios idiomas. Utiliza bibliotecas de internacionalizaci贸n (i18n) para gestionar las traducciones.
- Formato de fecha y hora: Formatea las fechas y horas seg煤n la configuraci贸n regional del usuario.
- Formato de n煤meros: Formatea los n煤meros seg煤n la configuraci贸n regional del usuario, incluidos los s铆mbolos de moneda y los separadores decimales.
- Conversi贸n de divisas: Si tu aplicaci贸n maneja divisas, proporciona opciones para la conversi贸n de divisas.
- Soporte para idiomas de derecha a izquierda (RTL): Si tu aplicaci贸n admite idiomas RTL (por ejemplo, 谩rabe, hebreo), aseg煤rate de que el dise帽o de tu interfaz de usuario se adapte correctamente.
- Accesibilidad: Aseg煤rate de que tu aplicaci贸n sea accesible para usuarios con discapacidades, siguiendo las pautas de WCAG. Esto ayuda a garantizar que los usuarios de todo el mundo puedan utilizar su aplicaci贸n de manera efectiva.
Ejemplo: Una plataforma internacional de comercio electr贸nico debe implementar una codificaci贸n de caracteres adecuada, traducir el contenido de su sitio web a varios idiomas y formatear fechas, horas y divisas seg煤n la regi贸n geogr谩fica del usuario para ofrecer una experiencia personalizada a usuarios de diversas ubicaciones.
El Futuro de los Motores de JavaScript
Los motores de JavaScript est谩n en constante evoluci贸n, con esfuerzos continuos para mejorar el rendimiento, agregar nuevas caracter铆sticas y mejorar la compatibilidad con los est谩ndares web. Estas son algunas tendencias clave a tener en cuenta:
- WebAssembly: WebAssembly (Wasm) es un formato de instrucci贸n binario que te permite ejecutar c贸digo escrito en varios lenguajes (como C, C++ y Rust) en el navegador a velocidades casi nativas. Los motores de JavaScript integran cada vez m谩s Wasm, lo que permite mejoras significativas de rendimiento para tareas computacionalmente intensivas.
- Optimizaci贸n JIT Adicional: Las t茅cnicas de compilaci贸n JIT se est谩n volviendo m谩s sofisticadas. Los motores exploran continuamente formas de optimizar la ejecuci贸n del c贸digo bas谩ndose en datos de tiempo de ejecuci贸n.
- Mejora de la Recolecci贸n de Basura: Los algoritmos de recolecci贸n de basura se refinan continuamente para minimizar las pausas y mejorar la gesti贸n de memoria.
- Soporte Mejorado para M贸dulos: El soporte para m贸dulos de JavaScript (m贸dulos ES) contin煤a evolucionando, lo que permite una organizaci贸n de c贸digo m谩s eficiente y una carga diferida.
- Estandarizaci贸n: Los desarrolladores de motores colaboran para mejorar la adherencia a las especificaciones de ECMAScript y mejorar la compatibilidad entre diferentes navegadores y tiempos de ejecuci贸n.
Conclusi贸n
Comprender el rendimiento en tiempo de ejecuci贸n de JavaScript es vital para los desarrolladores web, especialmente en el entorno global actual. Este art铆culo ha proporcionado una visi贸n general completa de V8, SpiderMonkey y JavaScriptCore, los principales actores en el panorama de los motores de JavaScript. Optimizar tu c贸digo JavaScript, junto con un uso eficiente del motor, es la clave para ofrecer aplicaciones web r谩pidas y receptivas. A medida que la web contin煤a evolucionando, tambi茅n lo har谩n los motores de JavaScript. Mantenerse informado sobre los 煤ltimos desarrollos y las mejores pr谩cticas ser谩 fundamental para crear experiencias de alto rendimiento y atractivas para usuarios de todo el mundo.